﻿//-----------------------------------------------------------------------
// <copyright file="ToolStripItem.cs" company="Noboru Project">
//      Copyright (c) Noboru Project. All rights reserved.
//
//      This source code is subject to terms and conditions of the New BSD License (BSD).
//      A copy of the license can be found in the License.html file at the root of this 
//      distribution. If you can not locate the New BSD License (BSD), please send an 
//      email to noboru_adm@live.com.
//      By using this source code in any fashion, you are agreeing to be bound by
//      the terms of the New BSD License (BSD) for Noboru.
//
//      You must not remove this notice, or any other, from this software.
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Noboru.Windows.Controls.Primitives
{
    /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.Class"]/*' />
    public abstract class ToolStripItem : ContentControl, IToolStripItem
    {
        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.IsCheckedProperty"]/*'/>
        public static readonly DependencyProperty CheckOnClickProperty = DependencyProperty.Register("CheckOnClick", typeof(bool), typeof(ToolStripItem), new PropertyMetadata(false));

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.GroupNameProperty"]/*'/>
        public static readonly DependencyProperty GroupNameProperty = DependencyProperty.Register("GroupName", typeof(string), typeof(ToolStripItem), new PropertyMetadata(ToolStripItem.GroupNamePropertyChangedCallback));

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.ImageMarginProperty"]/*' />
        public static readonly DependencyProperty ImageMarginProperty = DependencyProperty.Register("ImageMargin", typeof(Thickness), typeof(ToolStripItem), new PropertyMetadata(ToolStripItem.ImageMarginPropertyChangedCallback));

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.ImageSourceProperty"]/*' />
        public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(ToolStripItem), new PropertyMetadata(ToolStripItem.ImageSourcePropertyChangedCallback));

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.IsCheckedProperty"]/*'/>
        public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(ToolStripItem), new PropertyMetadata(true, ToolStripItem.IsCheckedPropertyChangedCallback));

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.IsEnabledProperty"]/*'/>
        public static readonly new DependencyProperty IsEnabledProperty = DependencyProperty.Register("IsEnabled", typeof(bool), typeof(ToolStripItem), new PropertyMetadata(true, ToolStripItem.IsEnabledPropertyChangedCallback));

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.TextImageRelationProperty"]/*' />
        public static readonly DependencyProperty TextImageRelationProperty = DependencyProperty.Register("TextImageRelation", typeof(TextImageRelation), typeof(ToolStripItem), new PropertyMetadata(TextImageRelation.ImageBeforeText, ToolStripItem.TextImageRelationPropertyChangedCallback));

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.TextMarginProperty"]/*' />
        public static readonly DependencyProperty TextMarginProperty = DependencyProperty.Register("TextMargin", typeof(Thickness), typeof(ToolStripItem), new PropertyMetadata(ToolStripItem.TextMarginPropertyChangedCallback));

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.TextProperty"]/*' />
        public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(ToolStripItem), new PropertyMetadata(ToolStripItem.TextPropertyChangedCallback));

        private static Dictionary<string, List<WeakReference>> _groupNameToElementsMap;

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.Constructor"]/*' />
        protected ToolStripItem()
        {
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.Checked"]/*' />
        public event RoutedEventHandler Checked;

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.Click"]/*' />
        public event RoutedEventHandler Click;

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.ImageMarginChanged"]/*' />
        public event EventHandler ImageMarginChanged;

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.ImageSourceChanged"]/*' />
        public event EventHandler ImageSourceChanged;

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.Indeterminate"]/*' />
        public event RoutedEventHandler Indeterminate;

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.TextChanged"]/*' />
        public event EventHandler TextChanged;

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.TextMarginChanged"]/*' />
        public event EventHandler TextMarginChanged;

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.Unchecked"]/*' />
        public event RoutedEventHandler Unchecked;

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.CheckOnClick"]/*' />
        public bool CheckOnClick
        {
            get
            {
                return (bool)this.GetValue(CheckOnClickProperty);
            }

            set
            {
                this.SetValue(CheckOnClickProperty, value);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.GroupName"]/*' />
        public string GroupName
        {
            get
            {
                return (string)this.GetValue(GroupNameProperty);
            }

            set
            {
                this.SetValue(GroupNameProperty, value);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.ImageMargin"]/*' />
        public Thickness ImageMargin
        {
            get
            {
                return (Thickness)this.GetValue(ImageMarginProperty);
            }

            set
            {
                this.SetValue(ImageMarginProperty, value);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.ImageSource"]/*' />
        public ImageSource ImageSource
        {
            get
            {
                return (ImageSource)this.GetValue(ImageSourceProperty);
            }

            set
            {
                this.SetValue(ImageSourceProperty, value);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.IsChecked"]/*'/>
        [TypeConverter(typeof(NullableBoolConverter))]
        public bool? IsChecked
        {
            get
            {
                return this.GetValue(IsCheckedProperty) as bool?;
            }

            set
            {
                this.SetValue(IsCheckedProperty, value);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.IsEnabled"]/*'/>
        public new bool IsEnabled
        {
            get
            {
                return (bool)this.GetValue(IsEnabledProperty);
            }

            set
            {
                this.SetValue(IsEnabledProperty, value);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.Text"]/*' />
        public string Text
        {
            get
            {
                return (string)this.GetValue(TextProperty);
            }

            set
            {
                this.SetValue(TextProperty, value);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.TextImageRelation"]/*' />
        public TextImageRelation TextImageRelation
        {
            get
            {
                return (TextImageRelation)this.GetValue(TextImageRelationProperty);
            }

            set
            {
                this.SetValue(TextImageRelationProperty, value);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.TextMargin"]/*' />
        public Thickness TextMargin
        {
            get
            {
                return (Thickness)this.GetValue(TextMarginProperty);
            }

            set
            {
                this.SetValue(TextMarginProperty, value);
            }
        }

        internal static DependencyObject GetVisualRoot(DependencyObject d)
        {
            DependencyObject root = d;
            for (;;)
            {
                FrameworkElement element = root as FrameworkElement;
                if (element == null)
                {
                    break;
                }

                DependencyObject parent = element.Parent as DependencyObject;
                if (parent == null)
                {
                    break;
                }

                root = parent;
            }

            return root;
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnChecked"]/*' />
        protected virtual void OnChecked(RoutedEventArgs e)
        {
            this.UpdateToolStripItemGroup();

            if (this.Checked != null)
            {
                this.Checked(this, e);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnClick"]/*' />
        protected virtual void OnClick(RoutedEventArgs e)
        {
            if (this.CheckOnClick)
            {
                if (this.IsChecked.Value)
                {
                    this.IsChecked = false;
                }
                else
                {
                    this.IsChecked = true;
                }
            }

            if (this.Click != null)
            {
                this.Click(this, e);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnGroupNameChanged"]/*' />
        protected virtual void OnGroupNameChanged(DependencyPropertyChangedEventArgs e)
        {
            string oldValue = (string)e.OldValue;
            if (!string.IsNullOrEmpty(oldValue))
            {
                UnregisterGroup(oldValue, this);
            }

            string newValue = (string)e.NewValue;
            if (!string.IsNullOrEmpty(newValue))
            {
                RegisterGroup(newValue, this);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnImageMarginChanged"]/*' />
        protected virtual void OnImageMarginChanged(DependencyPropertyChangedEventArgs e)
        {
            if (this.ImageMarginChanged != null)
            {
                this.ImageMarginChanged(this, new EventArgs());
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnImageSourceChanged"]/*' />
        protected virtual void OnImageSourceChanged(DependencyPropertyChangedEventArgs e)
        {
            if (this.ImageSourceChanged != null)
            {
                this.ImageSourceChanged(this, new EventArgs());
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnIndeterminate"]/*' />
        protected virtual void OnIndeterminate(RoutedEventArgs e)
        {
            if (this.Indeterminate != null)
            {
                this.Indeterminate(this, e);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnIsCheckedChanged"]/*' />
        protected virtual void OnIsCheckedChanged(DependencyPropertyChangedEventArgs e)
        {
            if (this.IsChecked.Value)
            {
                this.OnChecked(new RoutedEventArgs());
            }

            if (!this.IsChecked.Value)
            {
                this.OnUnchecked(new RoutedEventArgs());
            }
            else
            {
                this.OnIndeterminate(new RoutedEventArgs());
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnIsEnabledChanged"]/*' />
        protected virtual void OnIsEnabledChanged(DependencyPropertyChangedEventArgs e)
        {
            base.IsEnabled = this.IsEnabled;
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnTextChanged"]/*' />
        protected virtual void OnTextChanged(DependencyPropertyChangedEventArgs e)
        {
            if (this.TextChanged != null)
            {
                this.TextChanged(this, new EventArgs());
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnTextMarginChanged"]/*' />
        protected virtual void OnTextMarginChanged(DependencyPropertyChangedEventArgs e)
        {
            if (this.TextMarginChanged != null)
            {
                this.TextMarginChanged(this, new EventArgs());
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnTextImageRelationChanged"]/*' />
        protected virtual void OnTextImageRelationChanged(DependencyPropertyChangedEventArgs e)
        {
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.Unchecked"]/*' />
        protected virtual void OnUnchecked(RoutedEventArgs e)
        {
            if (this.Unchecked != null)
            {
                this.Unchecked(this, e);
            }
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnIsCheckedPropertyChanged"]/*' />
        protected virtual void OnIsCheckedPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            this.OnIsCheckedChanged(e);
        }

        /// <include file='Documentation\ToolStripItemDocumentation.uex' path='Members/Member[@for="ToolStripItem.OnIsEnabledPropertyChanged"]/*' />
        protected virtual void OnIsEnabledPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.IsEnabled = this.IsEnabled;

            this.OnIsEnabledChanged(e);
        }

        private static void GroupNamePropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((ToolStripItem)sender).OnGroupNameChanged(e);
        }

        private static void ImageMarginPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((ToolStripItem)sender).OnImageMarginChanged(e);
        }

        private static void ImageSourcePropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((ToolStripItem)sender).OnImageSourceChanged(e);
        }

        private static void IsCheckedPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((ToolStripItem)sender).OnIsCheckedPropertyChanged(e);
        }

        private static void IsEnabledPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((ToolStripItem)sender).OnIsEnabledPropertyChanged(e);
        }

        private static void TextImageRelationPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((ToolStripItem)sender).OnTextImageRelationChanged(e);
        }

        private static void TextMarginPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((ToolStripItem)sender).OnTextMarginChanged(e);
        }

        private static void TextPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((ToolStripItem)sender).OnTextChanged(e);
        }

        private static void UnregisterGroup(string groupName, ToolStripItem toolStripItem)
        {
            if (_groupNameToElementsMap != null)
            {
                List<WeakReference> elements = _groupNameToElementsMap[groupName];
                if (elements != null)
                {
                    RemoveExpiredElements(elements, toolStripItem);

                    if (elements.Count == 0)
                    {
                        _groupNameToElementsMap.Remove(groupName);
                    }
                }
            }
        }

        private static void RemoveExpiredElements(List<WeakReference> elements, object elementToRemove)
        {
            int index = 0;

            while (index < elements.Count)
            {
                object target = elements[index].Target;
                if ((target == null) || (target == elementToRemove))
                {
                    elements.RemoveAt(index);
                }
                else
                {
                    index++;
                }
            }
        }

        private static void RegisterGroup(string groupName, ToolStripItem toolStripItem)
        {
            if (_groupNameToElementsMap == null)
            {
                _groupNameToElementsMap = new Dictionary<string, List<WeakReference>>(1);
            }

            List<WeakReference> elements = null;
            if (!_groupNameToElementsMap.TryGetValue(groupName, out elements) || (elements == null))
            {
                elements = new List<WeakReference>(1);
                _groupNameToElementsMap[groupName] = elements;
            }
            else
            {
                RemoveExpiredElements(elements, null);
            }

            elements.Add(new WeakReference(toolStripItem));
        }

        private void UpdateToolStripItemGroup()
        {
            string groupName = this.GroupName;

            if (!string.IsNullOrEmpty(groupName))
            {
                DependencyObject visualRoot = GetVisualRoot(this);

                List<WeakReference> elements = _groupNameToElementsMap[groupName];

                int index = 0;
                while (index < elements.Count)
                {
                    ToolStripItem toolStripItem = elements[index].Target as ToolStripItem;
                    if (toolStripItem == null)
                    {
                        elements.RemoveAt(index);
                    }
                    else
                    {
                        if ((toolStripItem != this) && (toolStripItem.IsChecked == true) && (visualRoot == GetVisualRoot(toolStripItem)))
                        {
                            toolStripItem.IsChecked = false;
                        }

                        index++;
                    }
                }
            }
        }
    }
}
